package cn.ai.trigger.http;

import cn.ai.api.IRAGService;
import cn.ai.api.Response.Response;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
import org.redisson.api.RList;
import org.redisson.api.RedissonClient;
import org.springframework.ai.document.Document;
import org.springframework.ai.ollama.OllamaChatClient;
import org.springframework.ai.reader.tika.TikaDocumentReader;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.PgVectorStore;
import org.springframework.ai.vectorstore.SimpleVectorStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.PathResource;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestClient;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.List;
import java.util.Set;
import java.util.HashSet;
import java.util.concurrent.TimeUnit;

/**
 * @program AI-RAG
 * @author 济长述
 * @create 2025-08-16 10:18
 **/
@Slf4j
@RestController
@CrossOrigin("*")
@RequestMapping("/api/v1/rag")
public class RAGController implements IRAGService {

    // 需要忽略的目录名集合
    private static final Set<String> IGNORED_DIRECTORIES = new HashSet<>();
    // 需要忽略的文件名集合
    private static final Set<String> IGNORED_FILES = new HashSet<>();
    // 需要忽略的文件扩展名
    private static final Set<String> IGNORED_EXTENSIONS = new HashSet<>();

    static {
        // 初始化需要忽略的目录
        IGNORED_DIRECTORIES.add(".git");
        IGNORED_DIRECTORIES.add("node_modules");
        IGNORED_DIRECTORIES.add("venv");
        IGNORED_DIRECTORIES.add(".idea");
        IGNORED_DIRECTORIES.add(".vscode");

        // 初始化需要忽略的系统文件
        IGNORED_FILES.add(".DS_Store");       // macOS系统文件
        IGNORED_FILES.add("Thumbs.db");       // Windows系统文件
        IGNORED_FILES.add(".gitignore");      // Git忽略文件
        IGNORED_FILES.add(".gitattributes");  // Git属性文件

        // 初始化需要忽略的文件扩展名
        IGNORED_EXTENSIONS.add(".pack");      // Git打包文件
        IGNORED_EXTENSIONS.add(".idx");       // Git索引文件
    }

    @Resource
    private OllamaChatClient ollamaChatClient;
    @Resource
    private TokenTextSplitter tokenTextSplitter;
    @Resource
    private SimpleVectorStore simpleVectorStore;
    @Resource
    private PgVectorStore pgVectorStore;
    @Resource
    private RedissonClient redissonClient;


    @RequestMapping(value = "query_rag_tag_list" , method = RequestMethod.GET)
    @Override
    public Response<List<String>> queryRagTagList() {
        RList<String> elements = redissonClient.getList("ragTag");

        return Response.<List<String>>builder()
                .code("0000")
                .info("调用成功")
                .data(elements)
                .build();
    }

    @RequestMapping(value = "file/upload", method = RequestMethod.POST, headers = "content-type=multipart/form-data")
    @Override
    public Response<String> uploadFile(@RequestParam String ragTag, @RequestParam("file") List<MultipartFile> files) {
        log.info("上传知识库开始 {}",ragTag);
        for (MultipartFile file : files) {
            // 检查文件是否为空
            if (file.isEmpty()) {
                log.warn("跳过空文件: {}", file.getOriginalFilename());
                continue;
            }

            TikaDocumentReader documentReader = new TikaDocumentReader(file.getResource());
            List<Document> documents = documentReader.get();
            List<Document> documentSplitterList = tokenTextSplitter.apply(documents);

            documents.forEach(doc -> doc.getMetadata().put("knowledge", ragTag));
            documentSplitterList.forEach(doc -> doc.getMetadata().put("knowledge", ragTag));

            pgVectorStore.accept(documentSplitterList);

            RList<String> elements = redissonClient.getList("ragTag");
            if (!elements.contains(ragTag)){
                elements.add(ragTag);
            }
        }
        log.info("上传知识库完成 {}", ragTag);
        return Response.<String>builder().code("0000").info("调用成功").build();
    }

    @RequestMapping(value = "analyze_git_repository", method = RequestMethod.POST)
    @Override
    public Response<String> analyzeGitRepository(@RequestParam String repoUrl, @RequestParam String userName, @RequestParam String token) throws Exception {
        String localPath = "./git-cloned-repo";
        String repoProjectName = extractProjectName(repoUrl);
        log.info("克隆路径：{}", new File(localPath).getAbsolutePath());

        // 先删除已有目录（如果存在）
        deleteDirectoryWithRetry(new File(localPath), 3);

        // 克隆仓库
        Git git = null;
        try {
            git = Git.cloneRepository()
                    .setURI(repoUrl)
                    .setDirectory(new File(localPath))
                    .setCredentialsProvider(new UsernamePasswordCredentialsProvider(userName, token))
                    .call();

            // 遍历文件树，添加忽略逻辑
            Files.walkFileTree(Paths.get(localPath), new SimpleFileVisitor<>() {

                // 在进入目录前检查是否需要忽略
                @Override
                public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                    String dirName = dir.getFileName().toString();
                    if (IGNORED_DIRECTORIES.contains(dirName)) {
                        log.info("忽略目录: {}", dir);
                        return FileVisitResult.SKIP_SUBTREE; // 跳过整个子树
                    }
                    return FileVisitResult.CONTINUE; // 继续处理该目录
                }

                // 处理文件时检查是否需要忽略
                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                    String fileName = file.getFileName().toString();

                    // 检查文件名是否在忽略列表中
                    if (IGNORED_FILES.contains(fileName)) {
                        log.info("忽略文件: {}", file);
                        return FileVisitResult.CONTINUE;
                    }

                    // 检查文件扩展名是否在忽略列表中
                    int extIndex = fileName.lastIndexOf('.');
                    if (extIndex > 0) {
                        String extension = fileName.substring(extIndex).toLowerCase();
                        if (IGNORED_EXTENSIONS.contains(extension)) {
                            log.info("忽略扩展文件: {}", file);
                            return FileVisitResult.CONTINUE;
                        }
                    }

                    // 检查文件大小，跳过空文件
                    if (attrs.size() == 0) {
                        log.info("忽略空文件: {}", file);
                        return FileVisitResult.CONTINUE;
                    }

                    // 处理需要解析的文件
                    log.info("{} 遍历解析路径，上传知识库:{}", repoProjectName, file.getFileName());
                    try {
                        TikaDocumentReader reader = new TikaDocumentReader(new PathResource(file));
                        List<Document> documents = reader.get();

                        // 检查文档内容是否为空
                        if (documents.isEmpty() || (documents.size() == 1 &&
                                (documents.get(0).getContent() == null || documents.get(0).getContent().trim().isEmpty()))) {
                            log.warn("文件内容为空，跳过处理: {}", file);
                            return FileVisitResult.CONTINUE;
                        }

                        List<Document> documentSplitterList = tokenTextSplitter.apply(documents);

                        documents.forEach(doc -> doc.getMetadata().put("knowledge", repoProjectName));
                        documentSplitterList.forEach(doc -> doc.getMetadata().put("knowledge", repoProjectName));

                        pgVectorStore.accept(documentSplitterList);
                    } catch (Exception e) {
                        log.error("遍历解析路径，上传知识库失败:{}", file.getFileName(), e);
                    }

                    return FileVisitResult.CONTINUE;
                }

                @Override
                public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
                    log.info("访问文件失败: {} - {}", file.toString(), exc.getMessage());
                    return FileVisitResult.CONTINUE;
                }
            });
        } finally {
            // 关闭git连接
            if (git != null) {
                try {
                    git.close();
                } catch (Exception e) {
                    log.error("关闭Git连接失败", e);
                }
            }

            // 清理临时目录，增加重试机制
            deleteDirectoryWithRetry(new File(localPath), 3);
        }

        // 更新标签列表
        RList<String> elements = redissonClient.getList("ragTag");
        if (!elements.contains(repoProjectName)) {
            elements.add(repoProjectName);
        }

        log.info("遍历解析路径，上传完成:{}", repoUrl);

        return Response.<String>builder().code("0000").info("调用成功").build();
    }

    /**
     * 带重试机制的目录删除方法
     */
    private void deleteDirectoryWithRetry(File directory, int maxRetries) {
        if (!directory.exists()) {
            return;
        }

        int retryCount = 0;
        while (retryCount < maxRetries) {
            try {
                FileUtils.deleteDirectory(directory);
                log.info("成功删除目录: {}", directory.getAbsolutePath());
                return;
            } catch (IOException e) {
                retryCount++;
                log.warn("删除目录失败，将进行第{}次重试: {}", retryCount, directory.getAbsolutePath(), e);
                if (retryCount >= maxRetries) {
                    log.error("达到最大重试次数，删除目录失败: {}", directory.getAbsolutePath(), e);
                    return;
                }
                // 等待一段时间后重试
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                    return;
                }
            }
        }
    }

    private String extractProjectName(String repoUrl) {
        String[] parts = repoUrl.split("/");
        String projectNameWithGit = parts[parts.length - 1];
        return projectNameWithGit.replace(".git", "");
    }

}
